package com.bb.flow.service.imp.executor.flow;

import com.bb.flow.api.ExpressionCalc;
import com.bb.flow.api.entries.ConditionResult;
import com.bb.flow.api.entries.NotifyFlowEndInfo;
import com.bb.flow.api.entries.NotifyNodeExecuteInfo;
import com.bb.flow.api.entries.NotifyParentFlowInfo;
import com.bb.flow.api.type.ExecuteState;
import com.bb.flow.api.type.FlowEndType;
import com.bb.flow.config.AbstractConfigNode;
import com.bb.flow.config.FlowInfo;
import com.bb.flow.config.ToNode;
import com.bb.flow.config.enumtype.NodeType;
import com.bb.flow.config.node.JudgeConfigNode;
import com.bb.flow.service.api.NodeStateCallback;
import com.bb.flow.service.imp.AbstractFlowDriver;
import com.bb.flow.service.imp.AbstractNodeExecutor;
import com.bb.flow.service.imp.ExecuteFactory;
import com.bb.flow.util.Tools;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.util.Comparator;

/**
 * @author bigbeard
 * 流程执行器
 */
public class FlowDriverExecutor extends AbstractFlowDriver {

    private final static Logger logger = LoggerFactory.getLogger(FlowDriverExecutor.class);


    /**
     * 流程节点回调处理
     */
    private final transient NodeStateCallback nodeStateCallback = (node, flowInfo, executeState) -> {

        if (userCancelExecute(flowInfo)) {
            logger.info("用户在[{}]取消流程[{}/{}]执行", node.getNodeKey(),
                    flowInfo.getFlowTemplateName(), flowInfo.getFlowInstanceName());
            return;
        }

        NotifyParentFlowInfo parent = getParent(flowInfo);
        NotifyNodeExecuteInfo notifyNodeExecuteInfo = new NotifyNodeExecuteInfo(flowInfo.getFlowTemplateName()
                , node.getNodeKey()
                , executeState
                , flowInfo.getFlowContext()
                , node.getNodeType()
                , flowInfo.getFlowInstanceName()
                , parent);
        executeNodeExecuteStateCallback(notifyNodeExecuteInfo);
        if (executeState == ExecuteState.EXCEPTION
                || executeState == ExecuteState.NOT_EXECUTE
                || executeState == ExecuteState.TIME_OUT) {
            logger.error("流程模版[{}]执行过程中,节点[{}]返回异常,流程终止执行", flowInfo.getFlowTemplateName(), node.getNodeKey());
            NotifyFlowEndInfo notifyFlowEndInfo = new NotifyFlowEndInfo(
                    flowInfo.getFlowTemplateName(), flowInfo.getFlowInstanceName()
                    , FlowEndType.EXCEPTION_END, parent, flowInfo.getFlowContext());
            notifyFlowEndInfo.setFlowOtherInfo(flowInfo.getOtherInfo());
            executeFlowEndCallback(notifyFlowEndInfo);
            return;
        }
        if (executeState != ExecuteState.FINISH) {
            return;
        }
        executeFinishNext(node, flowInfo, executeState, notifyNodeExecuteInfo, parent);
    };

    /**
     * 当节点执行完成之后,系统开始执行下一个节点,此处会执行判断节点的条件计算和条件判断
     *
     * @param node                  当前完成的节点
     * @param flowInfo              流程
     * @param executeState          当前执行的节点状态
     * @param notifyNodeExecuteInfo 节点执行结果 监听返回对象
     * @param parent                父流程
     */
    private void executeFinishNext(AbstractConfigNode node, FlowInfo flowInfo, ExecuteState executeState,
                                   NotifyNodeExecuteInfo notifyNodeExecuteInfo, NotifyParentFlowInfo parent) {
        if (NodeType.JUDGE_NODE == node.getNodeType()) {
            JudgeConfigNode judgeConfigNode = (JudgeConfigNode) node;
            ExpressionCalc expressionCalc = judgeConfigNode.getCondition().getExpressionCalc();
            if (null == judgeConfigNode.getCondition()
                    || null == expressionCalc) {
                logger.error("流程模版[{}]中的判断节点[{}]未配置对应的计算信息,无法继续执行!",
                        flowInfo.getFlowTemplateName(), judgeConfigNode.getNodeKey());
                executeNodeExecuteStateCallback(notifyNodeExecuteInfo);
                return;
            }
            judgeConfigNode.getToNodes().sort(Comparator.comparingInt(ToNode::getOrderIndex));
            boolean flgHaveOne = false;

            ConditionResult conditionResult;
            try {
                conditionResult = judgeConfigNode.getConditionValueCalc().calcExpress(flowInfo.getFlowContext());
            } catch (Exception ex) {
                logger.error("流程模版[{}]实例[{}]执行判断节点[{}]调用条件计算时发生异常,错误信息:", flowInfo.getFlowTemplateName(),
                        flowInfo.getFlowInstanceName(), node.getNodeKey(), ex);
                notifyFlowEnd(FlowEndType.EXCEPTION_END, flowInfo, parent);
                return;
            }

            for (ToNode toNode : judgeConfigNode.getToNodes()) {
                boolean b;
                try {
                    b = expressionCalc.calc(toNode.getExpression(), flowInfo.getFlowContext(), conditionResult);
                } catch (Exception ex) {
                    logger.error("流程模版[{}]实例[{}]执行判断节点[{}]调用条件判断时发生异常,错误信息:", flowInfo.getFlowTemplateName(),
                            flowInfo.getFlowInstanceName(), node.getNodeKey(), ex);
                    notifyFlowEnd(FlowEndType.EXCEPTION_END, flowInfo, parent);
                    return;
                }
                if (b) {
                    flgHaveOne = true;
                    if (null == toNode.getNodeKey() || toNode.getNodeKey().isEmpty()) {
                        notifyFlowEnd(FlowEndType.NORMAL_END, flowInfo, parent);
                    } else {
                        AbstractConfigNode nextNode = getNodeByKey(toNode.getNodeKey(), flowInfo);
                        if (null != nextNode) {
                            prepareExecute(nextNode, flowInfo);
                            if (!toNode.isNeedCalcNext()) {
                                break;
                            }
                        } else {
                            logger.error("流程模版[{}]中的判断节点[{}]中的去向节点[{}]找不到,无法进行执行,流程异常结束",
                                    flowInfo.getFlowTemplateName(), judgeConfigNode.getNodeKey(),
                                    toNode.getNodeKey());
                            notifyFlowEnd(FlowEndType.EXCEPTION_END, flowInfo, parent);
                        }
                    }
                }
            }
            if (!flgHaveOne) {
                logger.warn("流程模版[{}]中的判断节点[{}]中的所有去向节点都不满足,系统取默认节点进行流转!",
                        flowInfo.getFlowTemplateName(), judgeConfigNode.getNodeKey());
                executeNextNode(flowInfo, node, executeState);
            }
        } else {
            executeNextNode(flowInfo, node, executeState);
        }
    }

    private void notifyFlowEnd(FlowEndType flowEndType, FlowInfo flowInfo, NotifyParentFlowInfo parent) {
        NotifyFlowEndInfo notifyFlowEndInfo = new NotifyFlowEndInfo(flowInfo.getFlowTemplateName(),
                flowInfo.getFlowInstanceName(), flowEndType, parent, flowInfo.getFlowContext());
        notifyFlowEndInfo.setFlowOtherInfo(flowInfo.getOtherInfo());
        executeFlowEndCallback(notifyFlowEndInfo);
    }

    /**
     * 执行节点
     *
     * @param node     节点
     * @param flowInfo 流程
     */
    @Override
    public void executeNode(AbstractConfigNode node, FlowInfo flowInfo) {
        if (null == node) {
            logger.error("预执行节点为null");
            return;
        }
        NodeType nodeType = node.getNodeType();
        AbstractNodeExecutor newNodeExecutor = ExecuteFactory.createNewNodeExecutor(nodeType, this);
        assert newNodeExecutor != null;
        newNodeExecutor.setNodeStateCallback(nodeStateCallback);
        newNodeExecutor.execute(node, flowInfo);
    }

    private void executeNextNode(FlowInfo flowInfo, AbstractConfigNode nodex, ExecuteState executeState) {
        AbstractConfigNode nextNode = getDefaultNextNodeKey(nodex, flowInfo);
        if (null == nextNode) {
            if (Tools.isEmptyOrNull(nodex.getNextNodeKey())) {
                logger.debug("流程实例[{}]执行结束!", flowInfo.getFlowInstanceName());
            } else {
                logger.debug("流程实例[{}]中节点[{}]指向的节点[{}]找不到或者不存在,流程结束!",
                        flowInfo.getFlowInstanceName(), nodex.getNodeKey(), nodex.getNextNodeKey());
            }
            NotifyParentFlowInfo parent = getParent(flowInfo);
            NotifyFlowEndInfo notifyFlowEndInfo = new NotifyFlowEndInfo(
                    flowInfo.getFlowTemplateName()
                    , flowInfo.getFlowInstanceName()
                    , executeState == ExecuteState.EXCEPTION
                    ? FlowEndType.EXCEPTION_END : FlowEndType.NORMAL_END
                    , parent
                    , flowInfo.getFlowContext()
            );
            notifyFlowEndInfo.setFlowOtherInfo(flowInfo.getOtherInfo());
            executeFlowEndCallback(notifyFlowEndInfo);
        } else {
            prepareExecute(nextNode, flowInfo);
        }
    }

    private AbstractConfigNode getNodeByKey(String nodeKey, FlowInfo flowInfo) {
        if (null == flowInfo || null == flowInfo.getFlowNodes()) {
            return null;
        }
        return flowInfo.getFlowNodes().get(nodeKey);
    }
}
